/*
 * Pll.c
 *
 *  Created on: Oct 13, 2018
 *      Author: ax
 */

#include <Device.h>

#include <ti/csl/csl_pllc.h>
#include <ti/csl/cslr_device.h>
#include <ti/csl/csl_bootcfgAux.h>


//MAIN_PLL: 1000000000(main) = 100000000(input) / 1(pre) * 20(m) / 2(post)
#define PLL_MUL     20
#define PRE_DIV      1
#define POST_DIV     2
//PLL RATIO, all are fixed beside 2, 5, 8 for c6678.
#define PLL2_RATIO   2  //default is 2
#define PLL5_RATIO   5  //default is 5
#define PLL8_RATIO  64  //default is 64

//DDR 10000(MT/s) (/ 2) == 100 / 2 * 20 (/ 2)
#define Emif_PLL_MUL 10
#define Emif_PRE_DIV 1
#define Emif_POS_DIV 2   //(/2) we'd better keep it fixed.

//PASS 1050 (/ 3) == 100 / 2 * 21 (/ 3) [work @350M]
#define Pass_PLL_MUL 21
#define Pass_PRE_DIV 2
#define Pass_POS_DIV 3


static void __init _pllInit(void) {
    /*1. Usage Note 9: For optimal PLL operation, the ENSAT bit in the PLL control *
     * registers for the Main PLL, DDR3 PLL, and PA PLL should be set to 1.      *
     * The PLL initialization sequence in the boot ROM sets this bit to 0 and    *
     * could lead to non-optimal PLL operation. Software can set the bit to the  *
     * optimal value of 1 after boot                                             */
    CSL_FINS(hBootCfg->PA_PLL_CTL1, BOOTCFG_PA_PLL_CTL1_ENSAT, 1);
    CSL_FINS(hBootCfg->DDR3_PLL_CTL1, BOOTCFG_DDR3_PLL_CTL1_ENSAT, 1);

    /*2. Put the PLL in Bypass Mode                                                */
    CSL_FINS(hBootCfg->PA_PLL_CTL0, BOOTCFG_PA_PLL_CTL0_BYPASS, 1);
    CSL_FINS(hBootCfg->DDR3_PLL_CTL0, BOOTCFG_DDR3_PLL_CTL0_BYPASS, 1);

    /*3.1 Program the necessary multipliers/dividers and BW adjustments             */
    /* Set the divider values */
    Uint32 val = hBootCfg->DDR3_PLL_CTL0;
    CSL_FINS(val, BOOTCFG_DDR3_PLL_CTL0_PLLD, Emif_PRE_DIV - 1);
    CSL_FINS(val, BOOTCFG_DDR3_PLL_CTL0_CLKOD, Emif_POS_DIV - 1);
    /*3.2 Set the Multipler values */
    CSL_FINS(val, BOOTCFG_DDR3_PLL_CTL0_PLLM, Emif_PLL_MUL - 1);
    hBootCfg->DDR3_PLL_CTL0 = val;
    ;//PASS
    val = hBootCfg->PA_PLL_CTL0;
    CSL_FINS(val, BOOTCFG_PA_PLL_CTL0_PLLD, Pass_PRE_DIV - 1);
    CSL_FINS(val, BOOTCFG_PA_PLL_CTL0_CLKOD, Pass_POS_DIV - 1);
    CSL_FINS(val, BOOTCFG_PA_PLL_CTL0_PLLM, Pass_PLL_MUL - 1);
    hBootCfg->PA_PLL_CTL0 = val;

    /*4. Set the BWADJ */
    val = Pass_PLL_MUL / Pass_POS_DIV - 1;
    CSL_FINS(hBootCfg->PA_PLL_CTL0, BOOTCFG_DDR3_PLL_CTL0_BWADJ, val);
    val >>= 8;
    CSL_FINS(hBootCfg->PA_PLL_CTL1, BOOTCFG_DDR3_PLL_CTL1_BWADJ, val);
    ;
    val = Emif_PLL_MUL / Emif_POS_DIV - 1;
    CSL_FINS(hBootCfg->DDR3_PLL_CTL0, BOOTCFG_DDR3_PLL_CTL0_BWADJ, val);
    val >>= 8;
    CSL_FINS(hBootCfg->DDR3_PLL_CTL1, BOOTCFG_DDR3_PLL_CTL1_BWADJ, val);


    /*5. In PLL Controller, reset the PLL (bit 13) in DDR3PLLCTL1_REG register       */
    CSL_FINS(hBootCfg->DDR3_PLL_CTL1, BOOTCFG_DDR3_PLL_CTL1_PLLRST, 1);
    CSL_FINS(hBootCfg->PA_PLL_CTL1, BOOTCFG_DDR3_PLL_CTL1_PLLRST, 1);

    /*Wait for PLL to lock min 5 micro seconds*/
    nop(7000);

    /*In DDR3PLLCTL1_REG, write PLLRST = 0 to bring PLL out of reset */
    CSL_FINS(hBootCfg->PA_PLL_CTL1, BOOTCFG_DDR3_PLL_CTL1_PLLRST, 0);
    CSL_FINS(hBootCfg->DDR3_PLL_CTL1, BOOTCFG_DDR3_PLL_CTL1_PLLRST, 0);

    /*Wait for PLL to lock min 50 micro seconds*/
    nop(70000);

    /* Put the PLL in PLL Mode */
    CSL_FINS(hBootCfg->PA_PLL_CTL0, BOOTCFG_DDR3_PLL_CTL0_BYPASS, 0);
    CSL_FINS(hBootCfg->DDR3_PLL_CTL0, BOOTCFG_DDR3_PLL_CTL0_BYPASS, 0);

    /* Wait for PLL to lock = min 500 ref clock cycles.
       With refclk = 100MHz, = 5000 ns = 5us
    nop(50000); */
}

void __init Pll_init(void) {
    Uint32 val;
    CSL_PllcRegsOvly pllc = (CSL_PllcRegsOvly)CSL_PLL_CONTROLLER_REGS;

    /* 1. Wait for Stabilization time (min 100 us) */
    nop(100000);

    /* 2. Check the status of BYPASS bit in SECCTL register,                   *
     *    execute the following steps if                                       *
     *    BYPASS == 1 (if bypass enabled), if BYPASS==0 then Jump to Step 3    */
    if (CSL_FEXT(pllc->SECCTL, PLLC_SECCTL_BYPASS)) {
        /* 2a. Usage Note 9: For optimal PLL operation, the ENSAT bit in the PLL control *
         * registers for the Main PLL, DDR3 PLL, and PA PLL should be set to 1.          *
         * The PLL initialization sequence in the boot ROM sets this bit to 0 and        *
         * could lead to non-optimal PLL operation. Software can set the bit to the      *
         * optimal value of 1 after boot                                                 */
        CSL_FINS(hBootCfg->CORE_PLL_CTL1, BOOTCFG_CORE_PLL_CTL1_ENSAT, 1);

        /* 2b. Clear PLLEN bit (bypass enabled in PLL controller mux) */
        CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLEN, BYPASS);

        /* 2c. Clear PLLENSRC bit (enable PLLEN to control PLL controller mux) */
        CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLENSRC, NO);

        /* 2d. Wait for 4 RefClks(to make sure the PLL controller *
         * mux switches properly to the bypass)                   *
         * Assuming slowest Ref clock of 25MHz, min: 160 ns nop */
        nop(225);

        /* 2e. Bypass needed to perform PWRDN cycle for C6670 and C6657              *
         * Needed on all devices when in NOBOOT, I2C or SPI boot modes               *
         * Ref: Figure 4-2 of http://www.ti.com/lit/ug/sprugv2a/sprugv2a.pdf         */
        CSL_FINS(pllc->SECCTL, PLLC_SECCTL_BYPASS, 1);

        /* 2f. Advisory 8: Multiple PLLs May Not Lock After Power-on Reset Issue     *
         * In order to ensure proper PLL startup, the PLL power_down pin needs to be *
         * toggled. This is accomplished by toggling the PLLPWRDN bit in the PLLCTL  *
         * register. This needs to be done before the main PLL initialization        *
         * sequence                                                                  *
         * Ref: Figure 4-1 of http://www.ti.com/lit/ug/sprugv2a/sprugv2a.pdf         */
        CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLPWRDN, YES);

        /* 2g. Stay in a loop such that the bit is set for 5 us (minimum) and        *
         * then clear the bit.                                                       */
        nop(14005);

        /* 2h. Power up the PLL */
        CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLPWRDN, NO);
    } else {/* 3. Enable BYPASS in the PLL contoller */
        /* 3a. Clear PLLEN bit (bypass enabled in PLL controller mux) */
        CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLEN, BYPASS);

        /* 3b. Clear PLLENSRC bit (enable PLLEN to control PLL controller mux) */
        CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLENSRC, NO);

        /* 3c. Wait for 4 RefClks (to make sure the PLL controller *
         * mux switches properly to bypass)                        *
         * Assuming slowest Ref clock of 25MHz, min: 160 ns nop  */
        nop(225);
    }

    /* 4 Program the necessary multipliers:
     * PLLM is split in two different registers. Program PLLM[5:0] in PLL multiplier
     *  control register (PLLM) and PLLM[12:6] in MAINPLLCTL0 register */
    val = (PLL_MUL - 1);
    CSL_FINS(pllc->PLLM, PLLC_PLLM_PLLM, val);   /* PLLM.L */
    val >>= 6;                /* PLLM.H bits[12:6]*/
    CSL_FINS(hBootCfg->CORE_PLL_CTL0, BOOTCFG_CORE_PLL_CTL0_PLLM, val);

    /* 5 Program the necessary BW adjustments:
     * BWADJ is split in two different registers. Program BWADJ[7:0] in
     *  MAINPLLCTL0 and BWADJ[11:8] in MAINPLLCTL1 register. BWADJ value
     *  must be set to (PLLM / POST_DIV - 1) */
    val = PLL_MUL / POST_DIV - 1;
    CSL_FINS(hBootCfg->CORE_PLL_CTL0, BOOTCFG_CORE_PLL_CTL0_BWADJ, val);
    val >>= 8;
    CSL_FINS(hBootCfg->CORE_PLL_CTL1, BOOTCFG_CORE_PLL_CTL1_BWADJ, val);

    /* 6. Set the pll divider (6 bit field) */
    CSL_FINS(hBootCfg->CORE_PLL_CTL0, BOOTCFG_CORE_PLL_CTL0_PLLD, PRE_DIV - 1);

    /* 7. Set the OUTPUT DIVIDE (4 bit field) in SECCTL */
    CSL_FINS(pllc->SECCTL, PLLC_SECCTL_CLKOD, POST_DIV - 1);

    /* 8. Set PLL dividers if needed */
    /* 8.a, go stat bit needs to be zero here                         *
     * Read the GOSTAT bit in PLLSTAT to make sure the bit reurns to 0 to   *
     * indicate that the GO operation has completed                         *
     * wait for the GOSTAT, but don't trap if lock is never read            */
    while(CSL_FEXT(pllc->PLLSTAT, PLLC_PLLSTAT_GOSTAT));

     /* 8.b, Set PLL dividers if needed */
    CSL_FINS(pllc->PLLDIV1_3[2-1], PLLC_PLLDIV1_3_RATIO, PLL2_RATIO - 1);
    CSL_FINS(pllc->PLLDIV4_16[5-4], PLLC_PLLDIV4_16_RATIO, PLL5_RATIO - 1);
    CSL_FINS(pllc->PLLDIV4_16[8-4], PLLC_PLLDIV4_16_RATIO, PLL8_RATIO - 1);

    /* 8.c, Program ALNCTLn, n = {2, 5, 8} - 1, bit 1, 4, 7 */
    pllc->ALNCTL |= 0x00000092;

    /* 8.d, Set GOSET bit in PLLCMD to initiate the GO operation to change the divide *
     * values and align the SYSCLKSs as programmed                                          */
    CSL_FINST(pllc->PLLCMD, PLLC_PLLCMD_GOSET, SET);

    /* 8.e, go stat bit needs to be zero here                         *
     * Read the GOSTAT bit in PLLSTAT to make sure the bit reurns to 0 to   *
     * indicate that the GO operation has completed                         *
     * wait for the GOSTAT, but don't trap if lock is never read            */
    while(CSL_FEXT(pllc->PLLSTAT, PLLC_PLLSTAT_GOSTAT));

    /* 9. Place PLL in Reset, In PLLCTL, write PLLRST = 1 (PLL is reset)        */
    CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLRST, YES);

    /* 10. Wait for PLL Reset assertion Time (min: 7 us)                        */
    nop(14000);

    /* 11. In PLLCTL, write PLLRST = 0 (PLL reset is de-asserted) */
    CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLRST, NO);

    /* 12. PLL Lock nop needs to be 500 RefClk periods * (PLLD + 1)           *
     * i.e., Wait for at least 500 * CLKIN cycles * (PLLD + 1) (PLL lock timer) *
     * Using 2000 25ns RefClk periods per DM                                    *
     * Wait for PLL to lock min 50 us                                           */
    nop(140056 >> 1);

    /* 13. In SECCTL, write BYPASS = 0 (enable PLL mux to switch to PLL mode) */
    CSL_FINS(pllc->SECCTL, PLLC_SECCTL_BYPASS, 0);

    /* 14. In PLLCTL, write PLLEN = 1 (enable PLL controller mux to switch to PLL mode) */
    CSL_FINST(pllc->PLLCTL, PLLC_PLLCTL_PLLEN, PLL);

    /* 15. The PLL and PLL Controller are now initialized in PLL mode - Completed. */

    //other peripheral's pll
    _pllInit();
}
